<?php
/**
 * GeryEngine
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this release in the file LICENSE
 *
 * @copyright  Copyright (c) 2007 Arjen Brouwer
 * @license    New BSD License
 * @author     Arjen Brouwer
 */

/**
 * This class is the abstract base class for all conventional 
 * Velocity Context  implementations.  Simply extend this class 
 * and implement the abstract routines that access your preferred 
 * storage method.
 *
 * Takes care of context chaining.
 *
 * Also handles / enforces policy on null keys and values :
 *
 * <ul>
 * <li> Null keys and values are accepted and basically dropped.
 * <li> If you place an object into the context with a null key, it
 *      will be ignored and logged.
 * <li> If you try to place a null into the context with any key, it
 *      will be dropped and logged.
 * </ul>
 *
 * The default implementation of this for application use is 
 * org.apache.velocity.VelocityContext.
 *
 * All thanks to Fedor for the chaining idea.
 *
 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 * @author <a href="mailto:fedor.karpelevitch@home.com">Fedor Karpelevitch</a>
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 * @version $Id: AbstractContext.java,v 1.8.8.1 2004/03/03 23:22:54 geirm Exp $
 */

abstract class AbstractGeryContext implements IGeryContext
{
    /**
     * The chained Context if any
     * @var Context $innerContext
     */
    private $innerContext = null;
  
    /** 
     * Implement to return a value from the context storage. 
     * <br><br>
     * The implementation of this method is required for proper
     * operation of a Context implementation in general
     * Velocity use.
     * 
     * @param String $key key whose associated value is to be returned
     * @return object stored in the context
     */
    abstract public function &internalGet($key);

    /** 
     * Implement to put a value into the context storage.
     * <br><br>
     * The implementation of this method is required for 
     * proper operation of a Context implementation in
     * general Velocity use.
     *
     * @param string $key key with which to associate the value
     * @param string $value value to be associated with the key
     * @return Object previously stored value if exists, or null
     */
    abstract public function internalPut($key, $value);

    /** 
     * Implement to determine if a key is in the storage.
     * <br><br>
     * Currently, this method is not used internally by 
     * the Velocity core. 
     *
     * @param string $key key to test for existance 
     * @return boolean true if found, false if not
     */
    abstract public function internalContainsKey($key);

    /** 
     * Implement to return an object array of key 
     * strings from your storage.
     * <br><br>
     * Currently, this method is not used internally by
     * the Velocity core.
     *
     * @return array of keys
     */
    abstract public function internalGetKeys();

    /** 
     * I mplement to remove an item from your storage.
     * <br><br>
     * Currently, this method is not used internally by
     * the Velocity core.
     *
     * @param key key to remove
     * @return object removed if exists, else null
     */
    abstract public function internalRemove($key);

    /**
     * Chaining constructor accepts a Context argument.
     * It will relay get() operations into this Context
     * in the even the 'local' get() returns null.
     * 
     * @param Context $inner context to be chained
     */
    public function __construct(IGeryContext $inner)
    {
    	if (! $inner instanceof IGeryContext)
    		$inner = null;
    		
        $this->innerContext = $inner;
    }

    /**
     * Adds a name/value pair to the context. 
     * 
     * @param string $key The name to key the provided value with.
     * @param Object $value The corresponding value.
     * @return Object that was replaced in the the IGeryContext if
     *       applicable or null if not.
     */
    public function put($key, $value)
    {
    	
    	/*
         * don't even continue if key or value is null
         */
        if ($key == null)
        {
            return null;
        }
        else if ($value === null)
        {
            return null;
        }
        
        if ($this->internalGet($key) !== null) {
        	return $this->internalPut($key, $value);
        } else if ($this->innerContext != null) {
        	return $this->innerContext->put($key, $value);
        } else {
        	return $this->internalPut($key, $value);
        }
    }

    /**
     * Gets the value corresponding to the provided key from the context.
     *
     * Supports the chaining context mechanism.  If the 'local' context
     * doesn't have the value, we try to get it from the chained context.
     *
     * @param string $key The name of the desired value.
     * @return Object The value corresponding to the provided key or null if
     *           the key param is null.
     */
    public function &get($key)
    {
        /*
         * punt if key is null
         */

        if ($key == null)
        {
            return null;
        }

        /*
         * get the object for this key.  If null, and we are chaining another IGeryContext
         * call the get() on it.
         */
        $o =& $this->internalGet($key);

        if ($o === null && $this->innerContext != null)
        {
            $o =& $this->innerContext->get($key);
        }
            
        return $o;
    }        

    /**
     * Indicates whether the specified key is in the context.  Provided for 
     * debugging purposes.
     *
     * @param key The key to look for.
     * @return boolean true if the key is in the context, false if not.
     */
    public function containsKey($key)
    {
        if ($key == null)
        {
            return false;
        }

        return $this->internalContainsKey($key);
    }        

    /**
     * Get all the keys for the values in the context
     * @return Object[] of keys in the Context. Does not return
     *        keys in chained context.
     */
    public function getKeys()
    {
        return $this->internalGetKeys();
    }

    /**
     * Removes the value associated with the specified key from the context.
     *
     * @param key The name of the value to remove.
     * @return    The value that the key was mapped to, or <code>null</code> 
     *          if unmapped.
     */
    public function remove($key)
    {
        if ($key == null)
        {
            return null;
        }

        return internalRemove(key);
    }   

    /**
     * returns innerContext if one is chained
     *
     * @return Context if chained, <code>null</code> if not
     */
    public function getChainedContext()
    {
        return $this->innerContext;
    }

}



